using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

using Imagination;
using Imagination.Display;
using Imagination.Physics;

namespace ImagineBalls
{
    public class BallsScreen : Screen
    {
        ImaginationEngine engine;

        ContentManager content;

        private Random rnd = new Random();
        private double timeSinceLastBall;

        Ball cueBall;

        VertexDeclaration vertexDeclaration;
        VertexBuffer vertexBuffer;
        IndexBuffer triangleListIndexBuffer;
        VertexPositionColor[] pointList;
        BasicEffect basicEffect;
        Camera camera;

        SpriteBatch batch;

        GamePadState previousPadState;

        public BallsScreen(ImagineBallsGame game) : base(game)
        {
            this.engine = game.Services.GetService(typeof(ImaginationEngine)) as ImaginationEngine;
            this.content = game.Content;
            
            // HACK: need to call these events at the end of constructor for the time being
            Initialize();
        }

        public override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            GraphicsDevice.VertexDeclaration = vertexDeclaration;
            basicEffect.View = camera.View;
            basicEffect.Projection = camera.Projection;

            basicEffect.Begin();
            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                DrawTriangleList();
                pass.End();
            }
            basicEffect.End();

            // HACK: need to do viewport culling of partitions
            foreach (Ball ball in engine.EnumerateThings())
            {
                ////Copy any parent transforms
                Matrix[] transforms = new Matrix[ball.Model.Bones.Count];
                ball.Model.CopyAbsoluteBoneTransformsTo(transforms);
                //Draw the model, a model can have multiple meshes, so loop
                foreach (ModelMesh mesh in ball.Model.Meshes)
                {
                    //This is where the mesh orientation is set, as well as our camera and projection
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.EnableDefaultLighting();
                        effect.Texture = Ball.Textures[ball.Number];
                        effect.World = transforms[mesh.ParentBone.Index] * ball.World;
                        effect.View = camera.View;
                        effect.Projection = camera.Projection;
                    }
                    //Draw the mesh, will use the effects set above.
                    mesh.Draw();
                }
            }
        }

        private void InitializeCamera()
        {
            camera = new Camera(GraphicsDevice);
            camera.Position = new Vector3(0.0f, 30.0f, 100.0f);
            camera.Refresh();
        }

        public override void Update(GameTime gameTime)
        {

            engine.UpdateThings(gameTime);
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.Insert))
                AddBall();

            if (keyState.IsKeyDown(Keys.D0))
            {
                camera.StopFollowing();
                camera.Position = new Vector3(0.0f, 30.0f, 100.0f);
                camera.Refresh();
            }


            if (keyState.IsKeyDown(Keys.D1))
            {
                camera.Follow(cueBall, new Vector3(0f, 2f, 10f));
            }

            if (keyState.IsKeyDown(Keys.D2))
            {
                camera.StopFollowing();
                camera.Position = new Vector3(100.0f, 30.0f, 20.0f);
                camera.Refresh();
            }


            if (keyState.IsKeyDown(Keys.D3))
            {
                camera.StopFollowing();
                camera.Position = new Vector3(20.0f, 120.0f, 20.0f);
                camera.Refresh();
            }

            if (keyState.IsKeyDown(Keys.F5))
            {
                InitPlayfield();
            }

            if (keyState.IsKeyDown(Keys.R))
            {
                Ball ball = AddBall();
                ball.Position = new Vector3(-120f, 1f, (float)(rnd.NextDouble() * 240) - 120);
                ball.Velocity = new Vector3(100f, 0f, 0f);

                if (rnd.Next() % 2 == 0)
                {
                    //  launch the ball from the other side
                    ball.Position *= new Vector3(-1, 1, 1);
                    ball.Velocity *= new Vector3(-1, 1, 1);
                }
            }

            GamePadState padState = GamePad.GetState(PlayerIndex.One);
            if (padState.IsConnected)
            {
                if (padState.Buttons.Y == ButtonState.Pressed &&
                    previousPadState.Buttons.Y == ButtonState.Released)
                    AddBall();

                float dirZ = padState.ThumbSticks.Left.X;
                float dirX = padState.ThumbSticks.Left.Y;

                cueBall.Moments = new Vector3((float)(-dirX), 0, (float)(-dirZ));

                //if (padState.ThumbSticks.Left.X != 0)
                //{
                //    double rotateFactor = padState.ThumbSticks.Left.X * 0.1;
                //    camera.Position += new Vector3(-(float)Math.Sin(rotateFactor), 0, -(float)Math.Cos(rotateFactor));
                //}

                if (padState.Buttons.B == ButtonState.Pressed)
                    InitPlayfield();
                if (padState.DPad.Up == ButtonState.Pressed)
                {
                    camera.Follow(cueBall, new Vector3(0f, 2f, 10f));
                }
                if (padState.Buttons.X == ButtonState.Pressed)
                {
                    //  let's fire off a random ball from the side

                    Ball ball = AddBall();
                    ball.Position = new Vector3(-120f, 1f, (float)(rnd.NextDouble() * 240) - 120);
                    ball.Velocity = new Vector3(100f, 0f, 0f);

                    if (rnd.Next() % 2 == 0)
                    {
                        //  launch the ball from the other side
                        ball.Position *= new Vector3(-1, 1, 1);
                        ball.Velocity *= new Vector3(-1, 1, 1);
                    }

                }

                if(padState.ThumbSticks.Right.X != 0)
                {
                    double rotateFactor = padState.ThumbSticks.Right.X;// *0.1;
                    camera.Target += new Vector3(-(float)Math.Sin(rotateFactor), 0, -(float)Math.Cos(rotateFactor));
                    //camera.Target += new Vector3((float)rotateFactor, 0, 0);
                }
                if (padState.ThumbSticks.Right.Y != 0)
                {
                    double rotateFactor = padState.ThumbSticks.Right.Y;// *0.1;
                    //camera.Position += new Vector3(-(float)Math.Sin(rotateFactor), 0, -(float)Math.Cos(rotateFactor));
                    camera.Target += new Vector3(0, (float)rotateFactor, 0);
                }
            }

            camera.Update(gameTime);
            previousPadState = padState;
        }

        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            InitializeCamera();
            if (loadAllContent)
            {
                Game.IsFixedTimeStep = false;

                InitializeEffect();
                InitializePointList();
                InitializeTriangleList();

                batch = new SpriteBatch(GraphicsDevice);
                //font = content.Load<Font>("Content\\Imagination\\debugFont");

                Ball.BallModel = content.Load<Model>("Content\\sphere");

                Ball.Textures = new Texture2D[16];
                for (int i = 0; i < 16; i++)
                {
                    Ball.Textures[i] = content.Load<Texture2D>("Content\\" + i.ToString());
                }

                InitPlayfield();
            }
        }

        public void InitPlayfield()
        {
            InitializeCamera();
            engine.ClearThings();

            cueBall = new Ball(0, new Vector3(0f, 1f, 20f));

            //  test: TetherConstraint
            //cueBall.Constraints.Add(new TetherConstraint(cueBall, new Vector3(0f, 1f, 20f), 5f));
            //cueBall.Constraints.Add(new MexicanJumpingConstraint(cueBall));
            engine.AddThing(cueBall);
            cueBall.InitPhysics();

            //for (int i = 1; i <= 50; i++)
            //    AddBall();
            for (int i = 1; i <= 1; i++)
                AddBall();

        }

        private void InitializeEffect()
        {
            vertexDeclaration = new VertexDeclaration(
                GraphicsDevice,VertexPositionColor.VertexElements);

            basicEffect = new BasicEffect(GraphicsDevice, null);
            basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
            basicEffect.View = camera.View;
            basicEffect.PreferPerPixelLighting = true;
            basicEffect.Projection = camera.Projection;
        }


        private void InitializePointList()
        {
            vertexDeclaration = new VertexDeclaration(
                GraphicsDevice, VertexPositionColor.VertexElements);

            pointList = new VertexPositionColor[4];

            pointList[0] = new VertexPositionColor(new Vector3(-120f, 0f, 120f), Color.Brown);
            pointList[1] = new VertexPositionColor(new Vector3(-120f, 0f, -120f), Color.Brown);
            pointList[2] = new VertexPositionColor(new Vector3(120f, 0f, -120f), Color.Brown);
            pointList[3] = new VertexPositionColor(new Vector3(120f, 0f, 120f), Color.Brown);

            // Initialize the vertex buffer, allocating memory for each vertex
            vertexBuffer = new VertexBuffer(GraphicsDevice,
                VertexPositionColor.SizeInBytes * (pointList.Length),
                ResourceUsage.None,
                ResourceManagementMode.Automatic);

            // Set the vertex buffer data to the array of vertices
            vertexBuffer.SetData<VertexPositionColor>(pointList);

        }

        private void InitializeTriangleList()
        {
            // Initialize an array of indices of type short
            short[] triangleListIndices = new short[6] { 0, 1, 2, 0, 2, 3 };

            // Initialize the index buffer, allocating memory for each index
            triangleListIndexBuffer = new IndexBuffer(GraphicsDevice,
                sizeof(short) * triangleListIndices.Length,
                ResourceUsage.None,
                ResourceManagementMode.Automatic,
                IndexElementSize.SixteenBits);

            // Set the data in the index buffer to our array
            triangleListIndexBuffer.SetData<short>(triangleListIndices);

        }

        private void DrawTriangleList()
        {
            GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0,
                VertexPositionColor.SizeInBytes);
            
            GraphicsDevice.Indices = triangleListIndexBuffer;
            this.basicEffect.DiffuseColor = Color.Brown.ToVector3();
            GraphicsDevice.DrawIndexedPrimitives(
                PrimitiveType.TriangleList,
                0,  // vertex buffer offset to add to each element of the index buffer
                0,  // minimum vertex index
                6, // number of vertices
                0,  // first index element to read
                2   // number of primitives to draw
                );
        }

        private Ball AddBall()
        {
            Vector3 position = new Vector3((float)(rnd.NextDouble() * 50 - 25), 15, (float)(rnd.NextDouble() * 50 - 25));
            position = new Vector3((rnd.Next(170) / 10) - 5, 30, (rnd.Next(210) / 10) - 10);
            
            //  torture test; all balls in one spot
            //position = new Vector3(0, 0.5f, 0);
            
            //if(radioSchemePool.Checked)
            Ball newBall = new Ball(((engine.ThingCount - 1) % 15) + 1, position);
            //DXdot.Physics.Force force = new DXdot.Physics.Force(0f, 0f, 100f, newBall);
            //force.IsTemporary = true;
            //newBall.Forces.Add(force);
            //else
            //	newBall = new Ball(host.Device, position);

            //  test: TetherConstraint
            //newBall.Constraints.Add(new TetherConstraint(newBall, new Vector3(0f, 30f, 0f), 25));
            //newBall.Constraints.Add(new MexicanJumpingConstraint(newBall));
            engine.AddThing(newBall);
            newBall.InitPhysics();
            return newBall;
        }
    }
}
